package com.openlogic.common.logic.decision.table.engine;

import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.stream.Collectors;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.noear.nami.Nami;
import org.noear.solon.Solon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson2.JSON;
import com.googlecode.aviator.AviatorEvaluator;
import com.openlogic.api.logic.dto.RuleSqlModelDto;
import com.openlogic.api.logic.service.RemoteRuleModelService;
import com.openlogic.common.core.utils.DateUtils;
import com.openlogic.common.logic.cache.DefaultSqlCache;
import com.openlogic.common.logic.constants.RuleConstants;
import com.openlogic.common.logic.constants.RuleTypeConstants;
import com.openlogic.common.logic.decision.common.DecisionEngine;
import com.openlogic.common.logic.decision.common.enums.Logic;
import com.openlogic.common.logic.decision.table.constants.CrossTableConstants;
import com.openlogic.common.logic.decision.table.constants.TableConstants;
import com.openlogic.common.logic.decision.table.model.CrossDecisionTable;
import com.openlogic.common.logic.decision.table.model.DecisionRow;
import com.openlogic.common.logic.decision.table.model.DecisionTableResultVo;
import com.openlogic.common.logic.run.ExecRuleHandler;
import com.openlogic.common.logic.utils.StackExceptionUtils;
import com.openlogic.common.plugins.db.api.utils.DataSourceUtils;
import com.openlogic.common.plugins.spi.enums.DbType;
import com.openlogic.datasource.api.param.ConnectionParam;

import jodd.db.connection.ConnectionProvider;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
* @ClassName: XCrossDecisionTableEngine
* @Description: 交叉决策表执行引擎
* @author weiyongpeng
* @date 2024年9月28日 上午9:54:57
 */
public class XCrossDecisionTableEngine extends DecisionEngine{
	
	private static RemoteRuleModelService remoteRuleModelService = Solon.context().getBean(RemoteRuleModelService.class);
	
	private static final Logger log = LoggerFactory.getLogger(XCrossDecisionTableEngine.class);	
	
	private static Object[] execLogArray = new Object[2];
	
	private static long DEFAULT_EXPIRE = 5 * 60 * 1000;
    
	private static DefaultSqlCache sqlCache = new DefaultSqlCache(100, DEFAULT_EXPIRE);
    
	private <T> T putCacheValue(String key, T value, long ttl) {
        sqlCache.put(key, value, ttl);
        return value;
    }

	private static final String execLogMsg = MessageFormat.format("{0} 正常信息 交叉决策表:【开始执行】", DateUtils.formatDate(DateUtils.getNowDate(), DateUtils.YYYY_MM_DD_HH_MM_SS));

	static {
		initExecLog(execLogArray,execLogMsg,RuleTypeConstants.decisionTable.getCode());
		if (remoteRuleModelService == null) {
			remoteRuleModelService = Nami.builder().create(RemoteRuleModelService.class);
			Solon.context().beanMake(RemoteRuleModelService.class);
		}
	}
	
	public static Object[] getExecLogArray() {
		return execLogArray;
	}

	/**
	* 描述：交叉决策表引擎
	* @Title: parseDecisionTable
	* @param crossTable
	* @throws Exception
	* @author weiyongpeng
	* @return DecisionTableResultVo
	* @date  2024年9月28日 下午3:04:12
	 */
	public static DecisionTableResultVo doDecisionTable(CrossDecisionTable crossTable,Map<String, Object> params,String id,StringBuilder logs) throws Exception{
		StopWatch stop = new StopWatch();
		stop.start();
		// 行条件参数队列-行条件桩
		Deque<Map<Integer, String>> rowConditionValueQueue = new ConcurrentLinkedDeque<>();
		Deque<Map<Integer, String>> rowConditionTmpQueue = new ConcurrentLinkedDeque<>();
		// 列条件参数队列-列条件桩
		Deque<Map<Integer, String>> colConditionValueQueue = new ConcurrentLinkedDeque<>();
		Deque<Map<Integer, String>> colConditionTmpQueue = new ConcurrentLinkedDeque<>();
		// 行条件参数队列-行条件桩-中文
		Deque<Map<Integer, String>> rowConditionTextQueue = new ConcurrentLinkedDeque<>();
		Deque<Map<Integer, String>> rowConditionTTmpQueue = new ConcurrentLinkedDeque<>();
		// 列条件参数队列-列条件桩-中文
		Deque<Map<Integer, String>> colConditionTextQueue = new ConcurrentLinkedDeque<>();
		Deque<Map<Integer, String>> colConditionTTmpQueue = new ConcurrentLinkedDeque<>();
		
		// 结果队列-结果桩
		Deque<Map<String, String>> resultValueQueue = new ConcurrentLinkedDeque<>();
		// 结果队列-结果桩-中文
		Deque<Map<String, String>> resultTextQueue = new ConcurrentLinkedDeque<>();
		
		// 获取交叉决策表数据集
		List<List<DecisionRow>> crosDecisions = crossTable.getDecisionRows();
		// 赋值参数标识
		String assignParamValue = crossTable.getAssignParamValue();
		// 参数赋值名称
		String assignParamText = crossTable.getAssignParamText();
		// 开始解析
		JSONArray crosDecisionArrayJson = JSONArray.fromObject(crosDecisions);
		for (Object crosDecisionArr : crosDecisionArrayJson) {
			JSONArray decisionRowArrayJson = JSONArray.fromObject(crosDecisionArr);
			// 每一行的元素
			for (Object row : decisionRowArrayJson) {
				// 行号
				Integer rowNum = (Integer)JSONObject.fromObject(row).get(CrossTableConstants.ROW_NUM_KEYWORDS);
//				log.info("\n");
//				log.info("rowNum:{} Row:{}", rowNum, row.toString());
				JSONArray decisionCellArrayJson = JSONArray.fromObject(JSONObject.fromObject(row).get(CrossTableConstants.DECISION_CELLS_KEYWORDS));
				for (Object cell : decisionCellArrayJson) {
					// 列号
					Integer cellNum = (Integer)JSONObject.fromObject(cell).get(CrossTableConstants.CELL_NUM_KEYWORDS);
					String cellType = (String)JSONObject.fromObject(cell).get(CrossTableConstants.CELL_TYPE_KEYWORDS);
					String cellValue = (String)JSONObject.fromObject(cell).get(CrossTableConstants.CELL_VALUE_KEYWORDS);
					String cellText = (String)JSONObject.fromObject(cell).get(CrossTableConstants.CELL_TEXT_KEYWORDS);
					// 是否跨列
					Integer colSpan = (Integer)JSONObject.fromObject(cell).get(TableConstants.COL_SPAN_KEYWORDS);
					// 是否跨行
					Integer rowSpan = (Integer)JSONObject.fromObject(cell).get(TableConstants.ROW_SPAN_KEYWORDS);
					// 行条件处理
					if (colSpan == 1 && cellType.equals(CrossTableConstants.CellTypeEnums.ROWCONDITION_DOT_CELL.getCode())) { // 不跨列
						Map<Integer, String> conditionValueMap = new HashMap<>();
						Map<Integer, String> conditionTextMap = new HashMap<>();
						Map<Integer, String> conditionTmpMap = new HashMap<>();
						Map<Integer, String> conditionTTmpMap = new HashMap<>();
						// 组装条件
						String conbinCondition = buildConditionValue(rowConditionTmpQueue, cellNum, cellValue);
						// 组装条件-中文
						String conbinTCondition = buildConditionValue(rowConditionTTmpQueue, cellNum, cellText);
						if(StringUtils.isNotBlank(conbinCondition)) {
							conditionValueMap.put(cellNum, conbinCondition);
							rowConditionValueQueue.push(conditionValueMap);
						}
						if(StringUtils.isNotBlank(conbinTCondition)) {
							conditionTextMap.put(cellNum, conbinTCondition);
							rowConditionTextQueue.push(conditionTextMap);
						}
						conditionTmpMap.put(cellNum, cellValue);
						rowConditionTmpQueue.push(conditionTmpMap);
						conditionTTmpMap.put(cellNum, cellText);
						rowConditionTTmpQueue.push(conditionTTmpMap);
					}else if (colSpan > 1 && cellType.equals(CrossTableConstants.CellTypeEnums.ROWCONDITION_DOT_CELL.getCode())) { // 跨列
						for (int j = 0; j < colSpan; j++) {
							Map<Integer, String> conditionValueMap = new HashMap<>();
							conditionValueMap.put((j+cellNum), cellValue);
							rowConditionTmpQueue.push(conditionValueMap);
							Map<Integer, String> conditionTextMap = new HashMap<>();
							conditionTextMap.put((j+cellNum), cellText);
							rowConditionTTmpQueue.push(conditionTextMap);
						}
					}
					// 列条件处理
					if (rowSpan == 1 && cellType.equals(CrossTableConstants.CellTypeEnums.COLCONDITION_DOT_CELL.getCode())) { // 不跨行
						Map<Integer, String> conditionValueMap = new HashMap<>();
						Map<Integer, String> conditionTextMap = new HashMap<>();
						Map<Integer, String> conditionTmpMap = new HashMap<>();
						Map<Integer, String> conditionTTmpMap = new HashMap<>();
						// 组装条件
						String conbinCondition = buildConditionValue(colConditionTmpQueue, rowNum, cellValue);
						// 组装条件-中文
						String conbinTCondition = buildConditionValue(colConditionTTmpQueue, rowNum, cellText);
						if(StringUtils.isNotBlank(conbinCondition)) {
							conditionValueMap.put(rowNum, conbinCondition);
							colConditionValueQueue.push(conditionValueMap);
						}
						if(StringUtils.isNotBlank(conbinTCondition)) {
							conditionTextMap.put(rowNum, conbinTCondition);
							colConditionTextQueue.push(conditionTextMap);
						}
						conditionTmpMap.put(rowNum, cellValue);
						colConditionTmpQueue.push(conditionTmpMap);
						conditionTTmpMap.put(rowNum, cellText);
						colConditionTTmpQueue.push(conditionTTmpMap);
					}else if (rowSpan > 1 && cellType.equals(CrossTableConstants.CellTypeEnums.COLCONDITION_DOT_CELL.getCode())) { // 跨行
						for (int j = 0; j < rowSpan; j++) {
							Map<Integer, String> conditionValueMap = new HashMap<>();
							conditionValueMap.put((j+rowNum), cellValue);
							colConditionTmpQueue.push(conditionValueMap);
							Map<Integer, String> conditionTextMap = new HashMap<>();
							conditionTextMap.put((j+rowNum), cellText);
							colConditionTTmpQueue.push(conditionTextMap);
						}
					}else if(cellType.equals(CrossTableConstants.CellTypeEnums.RESULT_CELL.getCode())) {
						Map<String, String> resultValueMap = new HashMap<>();
						resultValueMap.put((rowNum+"-"+cellNum), String.join(StringUtils.SPACE + "=" + StringUtils.SPACE, assignParamValue, cellValue));
						resultValueQueue.push(resultValueMap);
						Map<String, String> resultTextMap = new HashMap<>();
						resultTextMap.put((rowNum+"-"+cellNum), String.join(StringUtils.SPACE + "等于" + StringUtils.SPACE, assignParamText, cellText));
						resultTextQueue.push(resultTextMap);
					}
//					log.info("\n");
//					log.info("cellNum:{} cell:{}",cellNum, cell.toString());
				}
			}
		}
		colConditionValueQueue = CollectionUtils.isEmpty(colConditionValueQueue) ? colConditionTmpQueue : colConditionValueQueue;
		rowConditionValueQueue = CollectionUtils.isEmpty(rowConditionValueQueue) ? rowConditionTmpQueue : rowConditionValueQueue;
		colConditionTextQueue = CollectionUtils.isEmpty(colConditionTextQueue) ? colConditionTTmpQueue : colConditionTextQueue;
		rowConditionTextQueue = CollectionUtils.isEmpty(rowConditionTextQueue) ? rowConditionTTmpQueue : rowConditionTextQueue;

		// 进行行列条件的组合
		List<Map<String,String>> condition = rebuildCondition(rowConditionValueQueue,colConditionValueQueue);
		List<Map<String,String>> conditionText = rebuildCondition(rowConditionTextQueue,colConditionTextQueue);

//		log.info("condition:{}",mapCombineList(condition));
//		log.info("condition:{}",mapCombineList(conditionText));
//		log.info("resultValueQueue:{}",mapCombineDeque(resultValueQueue));
//		log.info("resultValueQueue:{}",mapCombineDeque(resultTextQueue));
		
		DecisionTableResultVo resultVo = execuate(mapCombineList(condition),mapCombineList(conditionText),mapCombineDeque(resultValueQueue),
												mapCombineDeque(resultTextQueue), params,id,logs, stop);
		if(Optional.ofNullable(resultVo.getExecStatus()).isPresent() && resultVo.getExecStatus()) {
			resultVo.setStartTime(DateUtils.formatDate(new Date(stop.getStartTime())));
			Object[] execLogArray = getExecLogArray();
			execLogArray[1] = resultVo;
			resultVo.setExecLog(JSONArray.fromObject(execLogArray).toString());
		}else if(Optional.ofNullable(resultVo.getExecStatus()).isPresent() && !resultVo.getExecStatus()) {
			resultVo.setStartTime(DateUtils.formatDate(new Date(stop.getStartTime())));
			Object[] execLogArray = getExecLogArray();
			execLogArray[1] = resultVo;
			resultVo.setResult(JSONArray.fromObject(execLogArray).toString());
		}else {
			resultVo.setStartTime(DateUtils.formatDate(new Date(stop.getStartTime())));
			Object[] execLogArray = getExecLogArray();
			resultVo.setExecLog("暂无匹配的条件");
			resultVo.setExecStatus(RuleConstants.NODE_EXEC_SUCCESS);
			execLogArray[1] = resultVo;
			resultVo.setExecLog(JSONArray.fromObject(execLogArray).toString());
		}
		return resultVo;
	}

	/**
	* 描述：组合条件值
	* @Title: buildConditionValue
	* @param rowConditionTmpQueue
	* @param cellNum
	* @param cellValue
	* @return
	* @author weiyongpeng
	* @date  2024年10月1日 下午5:28:17
	 */
	private static String buildConditionValue(Deque<Map<Integer, String>> rowConditionTmpQueue, Integer cellNum,
			String cellValue) {
		String conbinCondition = String.join("", rowConditionTmpQueue.stream()
							.filter(x -> x.containsKey(cellNum))
							.map(x -> 
								String.join(StringUtils.SPACE + Logic.AND.toString() + StringUtils.SPACE, 
											x.get(cellNum), 
											cellValue)
								)
							.collect(Collectors.toList()));
		return conbinCondition;
	}
	
	/**
	* 描述：执行交叉决策表
	* @Title: execuate
	* @param conditionMap
	* @param conditionTextMap
	* @param resultMap
	* @param params
	* @param id
	* @param logs
	* @author weiyongpeng
	 * @param stop 
	* @date  2024年10月1日 下午2:31:47
	 */
	private static DecisionTableResultVo execuate(Map<String, String> conditionMap,Map<String, String> conditionTextMap, Map<String, String> resultMap,
			Map<String, String> resultTextMap, Map<String, Object> params, String id, StringBuilder logs, StopWatch stop) {
		DecisionTableResultVo resultVo = new DecisionTableResultVo();
		resultVo.setId(id);
		Iterator<String> c = conditionMap.keySet().iterator();
		while (c.hasNext()) {
			try {
				String key = c.next();
				// 2、获取条件表达式 - 这里需要根据变量的类型，执行不同的AviatorExecutor
				String expressValue = doParseExpressValue(conditionMap.get(key), params);
				boolean result = (boolean) AviatorEvaluator.execute(expressValue, params, true);
				if (result) {
					Object caclRes = Optional.of(resultMap.get(key)).isPresent()
							? AviatorEvaluator.execute(doParseExpressValue(resultMap.get(key), params), params, true)
							: "";
					stop.stop();
					stop.reset(); // 重置Stopwatch
					stop.start(); // 重新开始计时
					logs.append(MessageFormat.format("决策执行id:【{0}】;决策执行开始时间:【{1}】;决策执行条件:【{2}】;决策执行动作:【{3}】;最终计算结果:【{4}】;决策执行结束时间:【{5}】;决策总耗时:【{6}】毫秒", id, DateUtils.formatDate(new Date(stop.getStartTime()), DateUtils.YYYY_MM_DD_HH_MM_SS), conditionTextMap.get(key), resultTextMap.get(key), caclRes, DateUtils.formatDate(new Date(stop.getStopTime()), DateUtils.YYYY_MM_DD_HH_MM_SS), stop.getTime()));
					resultVo.setExecLog(logs.toString());
					resultVo.setExecStatus(RuleConstants.NODE_EXEC_SUCCESS);
					resultVo.setResult(caclRes);
				}
			} catch (Exception e) {
				log.error("决策表执行异常:[{}]", e);
				resultVo.setExecStatus(RuleConstants.NODE_EXEC_ERROR);
				resultVo.setResult(MessageFormat.format("决策执行id:【{0}】;决策树执行异常,异常信息:【{1}】",id, StackExceptionUtils.getExceptionType(e).toString()));
				return resultVo;
			}
		}
		
		return resultVo;
	}
	
	private static Map<String,String> mapCombineDeque(Deque<Map<String, String>> list) {
		Map<String, String> map = new HashMap<>();
		for (Map<String, String> m : list) {
			Iterator<String> it = m.keySet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				map.put(key, m.get(key));
			}
		}
		return map;
	}
	
	private static Map<String,String> mapCombineList(List<Map<String, String>> list) {
		Map<String, String> map = new HashMap<>();
		for (Map<String, String> m : list) {
			Iterator<String> it = m.keySet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				map.put(key, m.get(key));
			}
		}
		return map;
	}

	/**
	* 描述：组合交叉评分卡条件
	* @Title: rebuildCondition
	* @param rowCondition
	* @param colCondition
	* @return
	* @author weiyongpeng
	* @date  2024年10月1日 下午2:30:53
	 */
	private static List<Map<String, String>> rebuildCondition(Deque<Map<Integer, String>> rowCondition,
			Deque<Map<Integer, String>> colCondition) {
		// 使用流处理
		List<Map<String, String>> combinedSet = colCondition.stream().flatMap(item1 -> rowCondition.stream()
				.flatMap(item2 -> item1.entrySet().stream().flatMap(entry1 -> item2.entrySet().stream().map(entry2 -> {
					Integer key1 = entry1.getKey();
					Integer key2 = entry2.getKey();
					String value1 = entry1.getValue();
					String value2 = entry2.getValue();

					// 组合key和value
					String combinedKey = String.join("-",key1+"",key2+"");
					String combinedValue = String.join(StringUtils.SPACE + Logic.AND.toString() + StringUtils.SPACE, value1, value2);

					// 创建组合项
					Map<String, String> combinedItem = new HashMap<>();
					combinedItem.put(combinedKey, combinedValue);
					return combinedItem;
				})))).collect(Collectors.toList());

		return combinedSet;
	}

	/**
	* 描述：解析表达式
	* @Title: doParseExpressValue
	* @param value
	* @param param
	* @return
	* @author weiyongpeng
	* @date  2024年12月27日 下午3:15:28
	 */
	@SuppressWarnings("unchecked")
	private static String doParseExpressValue(String value, Map<String, Object> param) {
		String expressValue = value;
		XCrossDecisionTableEngine xCrossDecisionTableEngine = new XCrossDecisionTableEngine();
		// 处理sql参数 @sqlVar2@|10
		JSONObject inputJson = JSONObject.fromObject(param);
		if (expressValue.contains("@") && ExecRuleHandler.extractStrings(expressValue).stream().filter(s -> s.split("\\.").length >= 2).collect(Collectors.toList()).size() > 0) {
			List<String> sqlVarList = ExecRuleHandler.extractStrings(expressValue).stream().filter(s -> s.split("\\.").length >= 2).collect(Collectors.toList());
			Iterator<String> it = inputJson.keys();
			for (String sqlVar : sqlVarList) {
				String[] sqlSplit = sqlVar.split("\\.");
				// 取出需要对比的字段
				String compareVar = sqlSplit[1];
				List<Object> columnValueList = new ArrayList<>();
				while (it.hasNext()) {
					String key = it.next();
					if (sqlSplit[0].equals(key)) {
						List<Map<String,Object>> listObjectFir = (List<Map<String,Object>>) com.alibaba.fastjson.JSONArray.parse((String)sqlCache.get(key));
						if (CollectionUtils.isEmpty(listObjectFir)) {
							listObjectFir = xCrossDecisionTableEngine.selectRuleSqlParamRes(inputJson, key, param);
						}
						columnValueList = listObjectFir.stream().filter(mapF -> Optional.ofNullable(mapF.get(compareVar)).isPresent()).map(mapV -> {
							return mapV.get(compareVar);
						}).collect(Collectors.toList());
						param.put(sqlVar.replaceAll("\\.", ""), columnValueList);
					}
				}
				expressValue = expressValue.replaceAll("\\@", "").replaceAll("\\.", "");;
			}
		}else { // 不包含sql参数的字段
			List<String> sqlVarList = ExecRuleHandler.extractStrings(expressValue).stream().collect(Collectors.toList());
			for (String key : sqlVarList) {
				List<Map<String,Object>> listObjectFir = Optional.ofNullable(sqlCache.get(key)).isPresent() ? 
						(List<Map<String,Object>>) com.alibaba.fastjson.JSONArray.parse((String)sqlCache.get(key)) : 
							xCrossDecisionTableEngine.selectRuleSqlParamRes(inputJson, key, param);
				param.put(key, listObjectFir);
			}
			expressValue = expressValue.replaceAll("\\@", "");
		}
		return expressValue;
	}
	
	/**
	* 描述：查询sql参数
	* @Title: selectRuleSqlParamRes
	* @param inputjson
	* @param key
	* @param env
	* @return
	* @author weiyongpeng
	* @date  2024年12月27日 下午3:15:03
	 */
	@SuppressWarnings("unchecked")
	private List<Map<String,Object>> selectRuleSqlParamRes(JSONObject inputjson, String key, Map<String, Object> env) {
		String sqlexp;
		// 1、查询sql语句，并执行sql select SQLEXP FROM CONFIG WHERE NAME = ${name}; 所以决策入参正常传递
		RuleSqlModelDto sqlInfoMap = remoteRuleModelService.listRuleModelInner(key).getData();
		String dbType = sqlInfoMap.getDbType();
		ConnectionParam connectionParam = JSON.parseObject(sqlInfoMap.getConnectionParams(), ConnectionParam.class);
		sqlexp = sqlInfoMap.getSqlexp();
		Iterator<String> its = inputjson.keys();
		if(sqlexp.contains("${")) { // 如果有条件参数
			while (its.hasNext()) {
				String key2 = its.next(); 
				if(!key2.equals("everyNodeList") && !key2.equals("areaInfo")) {
					String tmp = "\\$\\{" + key2 + "\\}";
					sqlexp = sqlexp.replaceAll(tmp , "'"+inputjson.getString(key2)+"'");
				}
			}
		}
		// 执行sql获取结果json
		ConnectionProvider connectionProvider = DataSourceUtils.getJdbcTemplate(DbType.getDbType(dbType),
				connectionParam);
		PreparedStatement prepareStatement;
		try {
			prepareStatement = connectionProvider.getConnection().prepareStatement(sqlexp);
			// 2、转化结果集为json 调用字符串的函数 决策设置需要标识出sql参数 加一个#标识 并缓存
			String sqlStr = (String) Optional.ofNullable(sqlCache.get(key)).orElse(
					putCacheValue(key, JSON.toJSONString(prepareStatement.executeQuery(sqlexp)), DEFAULT_EXPIRE));
			env.put(key, sqlStr);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return (List<Map<String,Object>>) com.alibaba.fastjson.JSONArray.parse((String)sqlCache.get(key));
	}
}
